package tx.algoritmiek.week6;

public class HashTable {
    /**
     * Constants
     */
    private static final int PRIME_SIZE = 5;
    private static final int EMPTY = -1000000;
    private static final int DELETED = -1000001;
    private static final int MIN_SIZE = 5;
    private static final double LOAD_FACTOR = 0.75;

    /**
     * Fields
     */
    private int[] array;
    private int elementCount = 0;

    /**
     * Default constructor.
     * 
     * @param capacity
     *            Capacity of the internal array.
     */
    public HashTable(int capacity) {
        if(capacity < MIN_SIZE) {
            throw new IllegalArgumentException(
                    "Capacity cannot be smaller than " + MIN_SIZE);
        }
        // Set size of the array
        this.array = new int[capacity];

        // Set all elements to an initial value
        for(int n = 0; n < capacity; n++) {
            array[n] = EMPTY;
        }
    }

    private int primaryHashcode(int key) {
        return key % this.array.length;
    }

    private int secundaryHashcode(int key) {
        return PRIME_SIZE - (key % PRIME_SIZE);
    }

    private int nextPosition(int pos, int step) {
        int position = pos + step;
        if(position >= this.array.length) {
            position %= this.array.length;
        }
        return position;
    }

    /**
     * Finds the element with the given value.
     * 
     * @param value
     *            The value to look for.
     * @return The position of the found value.
     */
    public int find(int value) {
        int position = primaryHashcode(value), step = secundaryHashcode(value);

        while(array[position] != EMPTY) {
            if(array[position] != DELETED && array[position] == value) {
                return position;
            }

            position = nextPosition(position, step);
        }

        return -1;
    }

    /**
     * Inserts an element with the given value. This method is thread-safe.
     * 
     * @param value
     *            The value to insert.
     * @return The position of the inserted value;
     * @throws IllegalStateException
     *             When the maximum load is reached and no more elements can be
     *             inserted.
     */
    public int insert(int value) {
        if(this.elementCount >= this.array.length * LOAD_FACTOR) {
            throw new IllegalStateException(
            "Reached maximum load, cannot insert more elements.");
        }
        int position = primaryHashcode(value), step = secundaryHashcode(value);

        synchronized(this.array) {
            while(array[position] != EMPTY && array[position] != DELETED) {
                position = nextPosition(position, step);
            }

            System.out.println("Insert value: " + value + ", at position: " + position);
            array[position] = value;
            this.elementCount++;
        }

        return position;
    }

    /**
     * Removes the element with the given value. This method is thread-safe.
     * 
     * @param value
     *            The element to be removed.
     * @return The position of the removed item or -1 if no item could be found.
     */
    public int remove(int value) {
        int position = find(value);
        if(position != -1) {
            synchronized(this.array) {
                System.out.println("Delete value: " + value + ", at position: " + position);
                array[position] = DELETED;
                this.elementCount--;
            }
        }

        return position;
    }
}
